use std::collections::HashSet;

use libp2p::{PeerId, core::Multiaddr, gossipsub::MessageId, request_response::ResponseChannel};
use tokio::sync::oneshot;

use tracing::{debug, error, info};

use crate::ProvidingContentId;
use crate::network_core::P2pNetWorkError;
use crate::network_core::network_core_impl::combined_behaviour::ContentResponse;

use crate::BcP2pError;
use crate::network_api::{P2PCommandOfApp, P2pNetWorkHandlerForApp};

impl P2pNetWorkHandlerForApp {
    pub async fn broadcast(&self, topic: &str, data: Vec<u8>) -> Result<MessageId, BcP2pError> {
        debug!("Broadcast message to topic ({topic})");
        let (tx, rx) = oneshot::channel();
        let _ = self
            .command_tx
            .send(P2PCommandOfApp::Broadcast {
                topic: topic.to_string(),
                data,
                cmd_ack_sender: tx,
            })
            .await;
        let result = rx.await.expect("Sender not to be dropped.");

        match result {
            Ok(msg_id) => {
                info!("Broadcast message to topic ({topic}) done. Message id: {msg_id}");
                Ok(msg_id)
            }
            Err(err) => {
                error!("Broadcast message to topic ({topic}) failed. Error: {err}");
                Err(err)
            }
        }
    }

    pub async fn register_topic(&self, topic: &str) {
        let _ = self
            .command_tx
            .send(P2PCommandOfApp::RegisterTopic {
                topic: topic.to_string(),
            })
            .await;
    }

    pub async fn start_sync(&self, peer: PeerId, chunk_ids: Vec<String>) {
        let _ = self
            .command_tx
            .send(P2PCommandOfApp::StartSync { peer, chunk_ids })
            .await;
    }
}

impl P2pNetWorkHandlerForApp {
    // /// Listen for incoming connections on the given address.
    // pub async fn start_listening(&mut self, addr: Multiaddr) -> Result<(), P2pNetWorkError> {
    //     println!("The P2P network listening on {addr}");
    //     let (sender, receiver) = oneshot::channel();
    //     self.command_sender
    //         .send(UserCommand::StartListening {
    //             addr,
    //             cmd_ack_sender: sender,
    //         })
    //         .await
    //         .expect("Command receiver not to be dropped.");
    //     receiver.await.expect("Sender not to be dropped.")
    // }

    /// Dial the given peer at the given address.
    pub async fn dial(
        &mut self,
        peer_id: PeerId,
        peer_addr: Multiaddr,
    ) -> Result<(), P2pNetWorkError> {
        info!("Dialing {peer_id} at {peer_addr}");
        let (sender, receiver) = oneshot::channel();
        self.command_tx
            .send(P2PCommandOfApp::Dial {
                peer_id,
                peer_addr,
                cmd_ack_sender: sender,
            })
            .await
            .expect("Command receiver not to be dropped.");
        receiver.await.expect("Sender not to be dropped.")
    }

    /// Advertise the local node as the provider of the given content on the DHT.
    pub async fn start_providing(&self, providing_content_id: &ProvidingContentId) {
        info!("Start providing content({providing_content_id})");
        let (sender, receiver) = oneshot::channel();
        self.command_tx
            .send(P2PCommandOfApp::StartProviding {
                providing_content_id: providing_content_id.clone(),
                cmd_ack_sender: sender,
            })
            .await
            .expect("Command receiver not to be dropped.");
        receiver.await.expect("Sender not to be dropped.");
    }

    /// Find the providers for the given content on the DHT.
    pub async fn get_providers(
        &self,
        providing_content_id: &ProvidingContentId,
    ) -> HashSet<PeerId> {
        info!("Get providers {providing_content_id}");
        let (sender, receiver) = oneshot::channel();
        debug!("---Get providers {providing_content_id}");
        self.command_tx
            .send(P2PCommandOfApp::GetProviders {
                providing_content_id: providing_content_id.clone(),
                cmd_ack_sender: sender,
            })
            .await
            .expect("Command receiver not to be dropped.");
        debug!("+++Get providers");
        receiver.await.expect("Sender not to be dropped.")
    }

    pub async fn request_content(
        &self,
        peer: &PeerId,
        providing_content_id: &ProvidingContentId,
    ) -> Result<Vec<u8>, P2pNetWorkError> {
        info!("Request file {providing_content_id} from {peer}");
        let (response_sender, response_receiver) = oneshot::channel();
        self.command_tx
            .send(P2PCommandOfApp::RequestContent {
                providing_content_id: providing_content_id.clone(),
                peer: *peer,
                response_sender,
            })
            .await
            .expect("Command receiver not to be dropped.");
        response_receiver.await.expect("Sender not be dropped.")
    }

    pub async fn respond_content(
        &self,
        content: Result<Vec<u8>, String>,
        channel: ResponseChannel<ContentResponse>,
    ) {
        info!("Respond file request with file content");
        self.command_tx
            .send(P2PCommandOfApp::RespondContent { content, channel })
            .await
            .expect("Command receiver not to be dropped.");
    }

    pub async fn get_local_peer_id(&self) -> PeerId {
        info!("Get local peer id");
        let (sender, receiver) = oneshot::channel();
        self.command_tx
            .send(P2PCommandOfApp::GetLocalPeerId {
                cmd_ack_sender: sender,
            })
            .await
            .expect("Command receiver not to be dropped.");
        receiver.await.expect("Sender not to be dropped.")
    }
}
